#include "token.h"
#include <string.h>
#include <memory.h>
#include <ctype.h>
#include <stdarg.h>
#include <stdbool.h>
#include <assert.h>
#include "common.h"
#include "option.h"

// #define DEBUG_TOKEN

#if defined DEBUG_TOKEN && defined DEBUG
#define dbg_token(x) x
#else
#define dbg_token(...)
#endif

typedef struct{
	token_e kind;
	const char * name;
} id_info_t;

static const char * g_token_names[] = {
    "none",
	#define	TOKEN(kind,name)	name,
	#include "token_i.h"
};

auto_cmem_t g_mem_pool;

token_builder g_builder;

char* mstrdup(const char* s)
{
    return cmem_strdup(&g_mem_pool, s);
}

static char g_wide_token_names[COUNTOF(g_token_names)][24];

const char* get_token_name(token_e kind) {
	return g_token_names[kind];
}

const char* get_wide_token_name(token_e kind) {
    if (g_wide_token_names[0][0] == 0) {
        for (int i = 0; i < COUNTOF(g_token_names); ++i) {
            strcpy(g_wide_token_names[i], g_token_names[i]);
            for (int j = (int)strlen(g_wide_token_names[i]); j < 8; ++j) {
                g_wide_token_names[i][j] = ' ';
            }
            if (strlen(g_wide_token_names[i]) >= 8)
                g_wide_token_names[i][8] = 0;
        }
    }
	return g_wide_token_names[kind];
}

void print_token(const token_t* tok)
{
    static int i = 0;
    printf("%s:%03d:%03d: %d. %s: %s",
        tok->loc.filename, tok->loc.line, tok->loc.column,
        i++, get_wide_token_name(tok->kind), ref_to_mstring(&tok->ss));
    if (tok->kind == TK_INTCONST) {
        printf("(0x%llx, %lld)\n", (long long)tok->i8, (long long)tok->i8);
    } else if (tok->kind == TK_DOUBLECONST) {
        printf("(%lf)\n", tok->d);
    } else {
        printf("\n");
    }
}

static id_info_t g_keys[] = {
    #define KEY_A(k, s) { k, s },
	#include "token_i.h"
};

static int g_id_size = 0;

static token_e get_id_kind(string_ref_t* ref)
{
    const char* id = ref_to_string(ref);
	for(int i = 0; i < COUNTOF(g_keys); i++){
		if(strcmp(id, g_keys[i].name) == 0){
			return g_keys[i].kind;
		}
	}
	return TK_ID;
}

void init_token(const char* buffer, int size, const char* filename/* = NULL */)
{
    g_builder.init_token(buffer, size, filename, &g_option);
}

void finish_token()
{
    cmem_free(&g_mem_pool);
}

static int get_char()
{
    return g_builder.get_char();
}

static int foresee_char()
{
    return g_builder.foresee_char();
}

void make_bak_token_point()
{
    g_builder.make_bak_token_point();
}

void restore_token_point()
{
    g_builder.restore_token_point();
}

token_t foresee_token()
{
    make_bak_token_point();
    token_t tok = get_token();
    restore_token_point();
    return tok;
}

void token_builder::print_line(const char* format, ...) {
    if (file_name) {
        printf("%s:%d:%d: ", file_name, line, colume);
    } else {
        printf("line %d:%d ", line, colume);
    }
    va_list ap;
    va_start(ap, format);
    vprintf(format, ap);
    va_end(ap);
}

static bool is_identifier_head(char ch){
	return ch == '_' || isalpha(ch);
}

static bool is_identifier(char ch){
	return ch == '_' || isalnum(ch);
}

static bool is_white(char ch){
	return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}

// ucc
static token_e GetTokenKindOfChar(char ch){
	for(int i = 0; i < COUNTOF(g_token_names); i++){
		if((strlen(g_token_names[i]) == 1) && (g_token_names[i][0] == ch)){
			return (token_e)i;
		}
	}
	return TK_NONE;
}

// ucc
static token_e GetTokenKindOfStr(char* s){
	for(int i = 0; i < COUNTOF(g_token_names); i++){
		if (strcmp(s, g_token_names[i]) == 0){
			return (token_e)i;
		}
	}
	return TK_NONE;
}

void token_builder::skip()
{
    do {
        if (is_white(c)) {
            if (c == '\n') {
                ++line;
                colume = 0;
            }
            c = get_char(); // next
        } else if ((g_option.slash_as_comment && c == '/' && foresee_char() == '/') ||
            (g_option.sharp_as_comment && c == '#')) {
            c = get_char();
            while (c != '\n' && c != EOF) {
                c = get_char();
            }
        } else if (c == '/' && foresee_char() == '*') {
            c = get_char(); // c = *
            while (!(c == EOF || (c == '*' && foresee_char() == '/'))) {
                if (c == '\n') {
                    ++line;
                    colume = 0;
                }
                c = get_char();
            }
            c = get_char(); // c = /
            if (c != '/') {
                mark_line();
                print_line("error: expect '*/'\n");
                ++error_count;
            }
            c = get_char(); // next
        } else {
            break;
        }
    } while (true);
}

char token_builder::parse_trans(token_t* tok)
{
    int n = 0;
    switch ((c = get_char()))
    {
    case '\'': c = get_char(); n = '\''; tok->ss.size++; break;
    case '\"': c = get_char(); n = '\"'; tok->ss.size++; break;
    case '\\': c = get_char(); n = '\\'; tok->ss.size++; break;
    case 'a':  c = get_char(); n = '\a'; tok->ss.size++; break;
    case 'b':  c = get_char(); n = '\b'; tok->ss.size++; break;
    case 'f':  c = get_char(); n = '\f'; tok->ss.size++; break;
    case 'n':  c = get_char(); n = '\n'; tok->ss.size++; break;
    case 'r':  c = get_char(); n = '\r'; tok->ss.size++; break;
    case 't':  c = get_char(); n = '\t'; tok->ss.size++; break;
    case 'v':  c = get_char(); n = '\v'; tok->ss.size++; break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    {
        for (int i = 0; i < 3; ++i)
        {
            if (c >= '0' && c <= '9') {
                n = (n << 3) + c - '0';
                c = get_char();
                tok->ss.size++;
            } else {
                break;
            }
        }
        break;
    }
    case 'x': {
        tok->ss.size++;
        for (int i = 0; i < 2; ++i)
        {
            c = get_char();
            tok->ss.size++;
            if (c >= 'a' && c <= 'f') {
                n = (n << 4) + (c - 'a') + 10;
            } else if (c >= 'A' && c <= 'F') {
                n = (n << 4) + (c - 'A') + 10;
            } else if (isdigit(c)) {
                n = (n << 4) + (c - '0');
            } else {
                printf("expect hex char, while got %c\n", c);
                break;
            }
        }
        c = get_char();
        break;
    }
    default:
        printf("the char: %c\n", c);
        ++error_count;
        n = EOF;
        assert(0);
        break;
    }
    return n;
}

void token_builder::parse_import(token_t* tok)
{
    init_string_ref(&tok->ss, buffer);
    skip();
    bool has_quote = c == '\"';
    if (has_quote) {
        c = get_char();
    }
    if (c != '\"') {
        tok->ss.offset = off;
        static char buf[4096];
        int count = 0;
        do {
            tok->ss.size++;
            if (count < sizeof(buf) - 1) {
                buf[count++] = c;
            }
            else {
                count++;
            }
            c = get_char();
        } while (!is_white(c) && c != EOF && c != '\"');
        if (count > (int)sizeof(buf) - 1) {
            mark_line();
            print_line("error: identifier too long\n");
            ++error_count;
            return;
        }
        buf[count] = 0;
        tok->val.s = mstrdup(buf);
    }
    if (has_quote) {
        if (c != '\"') {
            mark_line();
            print_line("error: expect \"\n");
            ++error_count;
            return;
        }
        c = get_char();
    }
}



token_t get_token()
{
    return g_builder.get_token();
}

token_t token_builder::get_token()
{
    token_t tok;
    memset(&tok, 0, sizeof(tok));
    tok.ss.base = buffer;
    tok.loc.filename = (char*)file_name;
TRY_AGAIN:
    if (c == EOF) {
        tok.loc.filename = file_name;
        tok.loc.line = line;
        tok.loc.column = colume;
        return tok;
    }
    skip();
    tok.loc.line = line;
    tok.loc.column = colume;
    dbg_token(fprint_location(stdout, &tok.loc));
    if (is_identifier_head(c)) {
        tok.ss.offset = off - 1;
        do {
            tok.ss.size++;
            c = get_char();
        } while (is_identifier(c));

        if (ref_to_string_is_full(&tok.ss)) {
            mark_line();
            print_line("error: identifier too long\n");
            ++error_count;
        }
        tok.kind = get_id_kind(&tok.ss);
        if (tok.kind == TK_IMPORT) {
            parse_import(&tok);
        }
    } else if (c == '"') {
        c = get_char(); // skip start '"'
        tok.ss.offset = off - 1;
        static char buf[4096];
        int count = 0;
        while (c != '"' && c != EOF && c != '\n') {
            tok.ss.size++;
            if (c == '\\') {
                char trans = parse_trans(&tok);
                if (count < sizeof(buf) - 1) {
                    buf[count++] = trans;
                } else {
                    count++;
                }
            } else {
                if (count < sizeof(buf) - 1) {
                    buf[count++] = c;
                } else {
                    count++;
                }
                c = get_char();
            }
        }

        if (c != '\"') {
            mark_line();
            print_line("error: expect '\"'\n");
            ++error_count;
        }
        if (count >= sizeof(buf)) {
            mark_line();
            print_line("error: string too long\n");
            ++error_count;
        }
        tok.kind = TK_STRING;
        buf[count] = 0;
        tok.val.s = mstrdup(buf);
        c = get_char(); // skip end '"'
    } else if (c == '\'') {
        c = get_char(); // skip start '"'
        tok.ss.offset = off - 1;
        tok.ss.size++;
        if (c == '\\') {
            tok.i8 = parse_trans(&tok);
        } else {
            tok.i8 = c;
            c = get_char();
        }
        if (c != '\'') {
            mark_line();
            print_line("error: expect '\''\n");
            ++error_count;
        }
        tok.kind = TK_CHAR;
        c = get_char(); // skip end '"'
    } else if (isdigit(c)) {
        tok.ss.offset = off - 1;
        // int
        tok.kind = TK_INTCONST;
        bool is_hex = false;
        bool is_bin = false;
        bool is_decimal = false;
		do{
            if (c == '_') {
                goto CONTINUE;
            }
            if (tok.ss.size == 1 && ref_char(&tok.ss, 0) == '0') {
                if (c == 'x' || c == 'X') {
                    if (is_bin) {
                        goto DIGITAL_ERROR;
                    }
                    is_hex = true;
                    goto CONTINUE;
                } else if (c == 'b' || c == 'B') {
                    if (is_hex) {
                        goto DIGITAL_ERROR;
                    }
                    is_bin = true;
                    goto CONTINUE;
                }
            }
            if (tok.ss.size > 0 && c == '.' && !is_hex && !is_bin && !is_decimal) {
                is_decimal = true;
                goto CONTINUE;
            }
            if (is_hex) {
                if (c >= 'a' && c <= 'f') {
                    tok.u8 = (tok.u8 << 4) + (c - 'a') + 10;
                } else if (c >= 'A' && c <= 'F') {
                    tok.u8 = (tok.u8 << 4) + (c - 'A') + 10;
                } else if (isdigit(c)) {
                    tok.u8 = (tok.u8 << 4) + (c - '0');
                } else {
                    break;
                }
            } else if (is_bin) {
                if (c >= '0' && c <= '1') {
                    tok.u8 = (tok.u8 << 1) + (c - '0');
                } else {
                    break;
                }
            } else {
                if (isdigit(c)) {
                    if (!is_decimal)
                        tok.u8 = tok.u8*10 + (c - '0');
                } else {
                    break;
                }
            }
            CONTINUE:
            {
                tok.ss.size++;
                c = get_char();
                continue;
            }
            DIGITAL_ERROR:
            {
                mark_line();
                print_line("error: illegal char %c .\n", c);
                ++error_count;
                break;
            }
		} while (true);
        if (ref_to_string_is_full(&tok.ss)) {
            print_line("warning: number too long, which will be truncated\n");
        }
        if (is_decimal) {
            tok.kind = TK_DOUBLECONST;
            sscanf(ref_to_string(&tok.ss), "%lf", &tok.d);
        } else if (!is_hex && !is_bin) {
            #ifdef INCLUDE_P4_KEY_WORDS
            expect(&tok, 'w', TK_WIDTH);
            expect(&tok, 'W', TK_WIDTH);
            #endif
        }
    } else {
        char s[3] = { c, 0, 0};
        tok.ss.offset = off - 1;
        if (c != EOF) {
            tok.kind = GetTokenKindOfChar(c);
            if(tok.kind != TK_NONE){
                tok.ss.size++;
                if ((c = get_char()) != EOF) {
                    s[1] = c;
                    int kind = GetTokenKindOfStr(s);
                    if (kind != TK_NONE) {
                        tok.ss.size++;
                        tok.kind = (token_e)kind;
                        c = get_char();
                    }
                }
            } else {
                mark_line();
                print_line("error: illegal char \'%x\' .\n", c);
                ++error_count;
                c = get_char();
                goto TRY_AGAIN;
            }
        }
    }
    // fprint_location(stdout, &tok.loc);
    // printf("token: %s\n", ref_to_string(&tok.ss));
    return tok;
}

void print_tokens()
{
    g_builder.print_tokens();
}

void token_builder::print_tokens()
{
    token_t token;
    int i = 0;
    do {
        token = get_token();
        print_token(&token);
        i++;
    } while (token.kind != TK_NONE);
}

int token_error_count()
{
    return g_builder.token_error_count();
}

const char* str(token_t* tok)
{
    return ref_to_string(&tok->ss);
}

char* new_auto_str(token_t* tok)
{
    return mstrdup(str(tok));
}
